"""
一些修复的代码
"""
from bct.tools.common.datetime_util import trans_datetime, parse_date_str, count_days
from bct.tools.http.calendar.trade_calendar_dto import TradingCalendarDTO
from bct.tools.http.trade.bct4_trade_dto import ProductType, CalcDaysType

product_snowball = [
    ProductType.SNOWBALL_FLOATING_PUT,
    ProductType.SNOWBALL_FLOATING_CALL,
    ProductType.SNOWBALL_FLOATING_NONE,
    ProductType.SNOWBALL_FLOATING_PARTIAL,
    ProductType.SNOWBALL_DOUBLE_DIGITAL,
    ProductType.SNOWBALL_PARACHUTE,
    ProductType.SNOWBALL_ENHANCE,
    ProductType.SNOWBALL_ENHANCE_PARTIAL_PROTECTIVE,
    ProductType.SNOWBALL_DISCOUNT_OPEN_PARTIAL_PROTECTIVE,
    ProductType.SNOWBALL_FLOATING_NONE_STEP_KNOCK_IN,
]

product_phoenix = [
    ProductType.PHOENIX_FLOATING_PARTIAL_DUE_COUPON,
    ProductType.PHOENIX_FLOATING_PARTIAL_IMMEDIATE_COUPON,
    ProductType.PHOENIX_FLOATING_NONE_DUE_COUPON,
    ProductType.PHOENIX_FLOATING_NONE_IMMEDIATE_COUPON
]


def common_patch_date(trade: dict, calendar: TradingCalendarDTO) -> bool:
    flag_chg = False
    # 返息支付日
    if 'prePayPremiumPaymentDate' in trade and trade['prePayPremiumPaymentDate'] is not None \
            and calendar.is_in_holiday(trans_datetime(trade['prePayPremiumPaymentDate'])):
        print("返息支付日 %s 不是交易日" % trade['prePayPremiumPaymentDate'])
        pre_pay_premium_payment_date = calendar.next_work_day(trans_datetime(trade['prePayPremiumPaymentDate']))
        trade['prePayPremiumPaymentDate'] = parse_date_str(pre_pay_premium_payment_date)
        flag_chg = True

    # 后端收益支付日
    if 'finalCouponPaymentDate' in trade and trade['finalCouponPaymentDate'] is not None \
            and calendar.is_in_holiday(trans_datetime(trade['finalCouponPaymentDate'])):
        print("后端收益支付日 %s 不是交易日" % trade['finalCouponPaymentDate'])
        final_coupon_payment_date = calendar.next_work_day(trans_datetime(trade['finalCouponPaymentDate']))
        trade['finalCouponPaymentDate'] = parse_date_str(final_coupon_payment_date)
        flag_chg = True

    # 后端期权费支付日
    if 'finalPremiumPaymentDate' in trade and trade['finalPremiumPaymentDate'] is not None \
            and calendar.is_in_holiday(trans_datetime(trade['finalPremiumPaymentDate'])):
        print("后端期权费支付日 %s 不是交易日" % trade['finalPremiumPaymentDate'])
        final_premium_payment_date = calendar.next_work_day(trans_datetime(trade['finalPremiumPaymentDate']))
        trade['finalPremiumPaymentDate'] = parse_date_str(final_premium_payment_date)
        flag_chg = True

    # 追加预付金支付日
    if 'secondPrePayDate' in trade and trade['secondPrePayDate'] is not None \
            and calendar.is_in_holiday(trans_datetime(trade['secondPrePayDate'])):
        print("追加预付金支付日 %s 不是交易日" % trade['secondPrePayDate'])
        second_pre_pay_date = calendar.next_work_day(trans_datetime(trade['secondPrePayDate']))
        trade['secondPrePayDate'] = parse_date_str(second_pre_pay_date)
        flag_chg = True

    return flag_chg


def process_snowball(trade: dict, calendar: TradingCalendarDTO) -> bool:
    """
    处理雪球日期问题
    注意期末观察日 和 起始日 trade上有交易上也有
    但是结算日只有product有
    :param trade:
    :param calendar:
    :return:
    """
    # flags
    flag_update_trade = False
    flag_common_date_chg = False
    flag_initial_date_chg = False
    flag_last_observation_date_chg = False
    flag_settlement_date_chg = False

    flag_observation_date_chg = False
    # trade info
    product = trade['products'][0]
    knock_out_observations = product['observations']
    calc_days_type = trade['calcDaysType']

    flag_common_date_chg = common_patch_date(trade, calendar)

    # 起始日
    initial_date = trans_datetime(trade['initialDate'])
    if calendar.is_in_holiday(initial_date):
        print("起始日 %s 不是交易日" % initial_date)
        fixed_date = calendar.next_work_day(initial_date)
        initial_date = fixed_date
        product['initialDate'] = parse_date_str(fixed_date)
        trade['initialDate'] = parse_date_str(fixed_date)
        flag_initial_date_chg = True

    last_observation_date = trans_datetime(product['lastObservationDate'])
    if calendar.is_in_holiday(last_observation_date):
        print("最后观察日 %s 不是交易日" % last_observation_date)
        fixed_date = calendar.next_work_day(last_observation_date)
        last_observation_date = fixed_date
        product['lastObservationDate'] = parse_date_str(fixed_date)
        trade['lastObservationDate'] = parse_date_str(fixed_date)
        # 这动了后结算日也要改
        product['settlementDate'] = parse_date_str(calendar.next_work_day(fixed_date, 2))
        flag_last_observation_date_chg = True

    settlement_date = trans_datetime(product['settlementDate'])
    if calendar.is_in_holiday(settlement_date):
        print("结算日 %s 不是交易日" % settlement_date)
        fixed_date = calendar.next_work_day(settlement_date)
        settlement_date = fixed_date
        product['settlementDate'] = parse_date_str(fixed_date)
        flag_settlement_date_chg = True

    # 如果任何一个日期变动了，则需要更新计算天数
    if flag_initial_date_chg or flag_last_observation_date_chg or flag_settlement_date_chg:
        # 计算天数类型 含头含尾+1 不含头尾-1
        # 计算天数
        product['countedDays'] = count_days(initial_date, last_observation_date, CalcDaysType(calc_days_type))
        trade['countedDays'] = product['countedDays']
        flag_update_trade = True

    # 如果最后观察日变动了，则直接将最后一个观察日的数据为修改后的数据
    last_obs_date = knock_out_observations[-1]
    if product['countedDays'] != last_obs_date['knockOutCountedDays'] or trade['countedDays'] != last_obs_date[
        'knockOutCountedDays']:
        print("天数不一致 %s - %s" % (product['countedDays'], last_obs_date['knockOutCountedDays']))
        flag_settlement_date_chg = True

    if last_obs_date and (
            flag_last_observation_date_chg or
            flag_settlement_date_chg or
            last_obs_date['knockOutSettlementDate'] != product['settlementDate'] or
            last_obs_date['knockOutObservationDate'] != trade['lastObservationDate']
    ):
        last_obs_date['knockOutObservationDate'] = trade['lastObservationDate']
        last_obs_date['knockOutSettlementDate'] = product['settlementDate']
        last_obs_date['knockOutCountedDays'] = count_days(
            initial_date, trans_datetime(last_obs_date['knockOutObservationDate']), CalcDaysType(calc_days_type))
        product['countedDays'] = last_obs_date['knockOutCountedDays']
        trade['countedDays'] = last_obs_date['knockOutCountedDays']
        flag_observation_date_chg = True

    # 这样观察日的最后一天已经ok了，那么开始修正其他的
    # 需要记录前一个日期，防止日期重叠，初始值定位起始日
    prev_obs_date = initial_date
    for obs in knock_out_observations[0:-1]:
        knock_out_observation_date = trans_datetime(obs['knockOutObservationDate'])
        knock_out_settlement_date = trans_datetime(obs['knockOutSettlementDate'])
        if prev_obs_date >= knock_out_observation_date:
            # 日期错误了，需要至少挪动到前一个观察日的后一天
            knock_out_observation_date = calendar.next_work_day(prev_obs_date, 1)
            knock_out_settlement_date = calendar.next_work_day(knock_out_observation_date, 2)
            obs['knockOutObservationDate'] = parse_date_str(knock_out_observation_date)
            obs['knockOutSettlementDate'] = parse_date_str(knock_out_settlement_date)
            obs['knockOutCountedDays'] = count_days(initial_date, knock_out_observation_date,
                                                    CalcDaysType(calc_days_type))
            prev_obs_date = knock_out_observation_date
            flag_observation_date_chg = True
        elif calendar.is_in_holiday(knock_out_observation_date) or calendar.is_in_holiday(knock_out_settlement_date):
            # 日期没错，那么只要处理工作日就行
            knock_out_observation_date = calendar.next_work_day(knock_out_observation_date)
            knock_out_settlement_date = calendar.next_work_day(knock_out_observation_date, 2)
            obs['knockOutObservationDate'] = parse_date_str(knock_out_observation_date)
            obs['knockOutSettlementDate'] = parse_date_str(knock_out_settlement_date)
            obs['knockOutCountedDays'] = count_days(initial_date, knock_out_observation_date,
                                                    CalcDaysType(calc_days_type))
            prev_obs_date = knock_out_observation_date
            flag_observation_date_chg = True
        elif flag_initial_date_chg:
            # 起始日变了也得算一下
            obs['knockOutCountedDays'] = count_days(initial_date, knock_out_observation_date,
                                                    CalcDaysType(calc_days_type))
            prev_obs_date = knock_out_observation_date
            flag_observation_date_chg = True
    return flag_update_trade or \
           flag_last_observation_date_chg or \
           flag_settlement_date_chg or \
           flag_initial_date_chg or \
           flag_observation_date_chg or \
           flag_common_date_chg


def process_phoenix(trade: dict, calendar: TradingCalendarDTO):
    """
    处理凤凰观察日问题
    :param trade:
    :param calendar:
    :return:
    """
    # flags
    flag_update_trade = False
    flag_common_date_chg = False
    flag_initial_date_chg = False
    flag_last_observation_date_chg = False
    flag_settlement_date_chg = False

    flag_observation_date_chg = False
    # trade info
    product = trade['products'][0]
    observations = product['observations']
    calc_days_type = trade['calcDaysType']

    flag_common_date_chg = common_patch_date(trade, calendar)

    # 起始日
    initial_date = trans_datetime(trade['initialDate'])
    if calendar.is_in_holiday(initial_date):
        print("起始日 %s 不是交易日" % initial_date)
        fixed_date = calendar.next_work_day(initial_date)
        initial_date = fixed_date
        product['initialDate'] = parse_date_str(fixed_date)
        trade['initialDate'] = parse_date_str(fixed_date)
        flag_initial_date_chg = True

    # 最后观察日
    last_observation_date = trans_datetime(product['lastObservationDate'])
    if calendar.is_in_holiday(last_observation_date):
        print("最后观察日 %s 不是交易日" % last_observation_date)
        fixed_date = calendar.next_work_day(last_observation_date)
        last_observation_date = fixed_date
        product['lastObservationDate'] = parse_date_str(fixed_date)
        trade['lastObservationDate'] = parse_date_str(fixed_date)
        # 这动了后结算日也要改
        product['settlementDate'] = parse_date_str(calendar.next_work_day(fixed_date, 2))
        flag_last_observation_date_chg = True

    # 结算日
    settlement_date = trans_datetime(product['settlementDate'])
    if calendar.is_in_holiday(settlement_date):
        print("结算日 %s 不是交易日" % settlement_date)
        fixed_date = calendar.next_work_day(settlement_date)
        settlement_date = fixed_date
        product['settlementDate'] = parse_date_str(fixed_date)
        flag_settlement_date_chg = True

    # 如果最后观察日变动了，则直接将最后一个观察日的数据为修改后的数据
    last_obs = observations[-1]
    if product['countedDays'] != last_obs['knockOutCountedDays'] or trade['countedDays'] != last_obs[
        'knockOutCountedDays']:
        print("天数不一致 %s - %s" % (product['countedDays'], last_obs['knockOutCountedDays']))
        flag_settlement_date_chg = True

    if last_obs and (flag_last_observation_date_chg or
                     flag_settlement_date_chg or
                     last_obs['knockOutSettlementDate'] != product['settlementDate'] or
                     last_obs['observationDate'] != trade['lastObservationDate']
    ):
        trade_last_observation_date = trade['lastObservationDate']
        product_settlement_date = product['settlementDate']

        last_obs['observationDate'] = trade_last_observation_date
        last_obs['knockOutSettlementDate'] = product_settlement_date
        last_obs['couponSettlementDate'] = product_settlement_date
        last_obs['knockOutCountedDays'] = count_days(
            initial_date, trans_datetime(trade_last_observation_date), CalcDaysType(calc_days_type))
        product['countedDays'] = last_obs['knockOutCountedDays']
        trade['countedDays'] = last_obs['knockOutCountedDays']
        flag_observation_date_chg = True

    # 这样观察日的最后一天已经ok了，那么开始修正其他的
    # 观察日(不含最后一个)
    # 需要记录前一个日期，防止日期重叠，初始值定位起始日
    prev_obs_date = initial_date
    for obs in observations[0:-1]:
        observation_date = trans_datetime(obs['observationDate'])
        knock_out_settlement_date = trans_datetime(obs['knockOutSettlementDate'])
        coupon_settlement_date = trans_datetime(obs['couponSettlementDate'])
        if prev_obs_date >= observation_date:
            # 日期错误了，需要至少挪动到前一个观察日的后一天
            observation_date = calendar.next_work_day(prev_obs_date, 1)
            knock_out_settlement_date = calendar.next_work_day(observation_date, 2)
            obs['observationDate'] = parse_date_str(observation_date)
            obs['knockOutSettlementDate'] = parse_date_str(knock_out_settlement_date)
            obs['couponSettlementDate'] = parse_date_str(knock_out_settlement_date)
            obs['knockOutCountedDays'] = count_days(initial_date, observation_date,
                                                    CalcDaysType(calc_days_type))
            flag_observation_date_chg = True
        elif calendar.is_in_holiday(observation_date) or calendar.is_in_holiday(knock_out_settlement_date) \
                or calendar.is_in_holiday(coupon_settlement_date):
            # 日期没错，那么只要处理工作日就行
            observation_date = calendar.next_work_day(observation_date)
            knock_out_settlement_date = calendar.next_work_day(observation_date, 2)
            obs['observationDate'] = parse_date_str(observation_date)
            obs['knockOutSettlementDate'] = parse_date_str(knock_out_settlement_date)
            obs['couponSettlementDate'] = parse_date_str(knock_out_settlement_date)
            obs['knockOutCountedDays'] = count_days(initial_date, observation_date,
                                                    CalcDaysType(calc_days_type))
            flag_observation_date_chg = True
        elif flag_initial_date_chg:
            # 起始日变了也得算一下
            obs['knockOutCountedDays'] = count_days(initial_date, observation_date,
                                                    CalcDaysType(calc_days_type))
            flag_observation_date_chg = True
        prev_obs_date = observation_date
    # 起始日变了也得算一下
    return flag_update_trade or \
           flag_last_observation_date_chg or \
           flag_settlement_date_chg or \
           flag_initial_date_chg or \
           flag_observation_date_chg or \
           flag_common_date_chg


def fix_trade(trade: dict, calendar: TradingCalendarDTO):
    """
    修复
    :param client:
    :return:
    """

    product_type = trade['productType']
    if ProductType(product_type) in product_snowball:
        return process_snowball(trade, calendar)
    elif ProductType(product_type) in product_phoenix:
        return process_phoenix(trade, calendar)
    else:
        return common_patch_date(trade, calendar)
